iT邦幫忙

2021 iThome 鐵人賽

DAY 17
1
Modern Web

用30天更加認識 React.js 這個好朋友系列 第 17

Day17-Redux 篇-用 Redux Toolkit 實作範例

  • 分享至 

  • xImage
  •  

在第 15 天的文章中我們做了一個範例,現在我們要用 Redux Toolkit 去改寫它。

第一步

我們將原本範例的 store 和 reducer 做改寫。

調整前:

// store/index.js
import { createStore } from "redux";

const initialState = { counter: 0, showCounter: true };

const counterReducer = (state = initialState, action) => {
  if (action.type === "increment") {
    return {
      counter: state.counter + 1,
      showCounter: state.showCounter
    };
  }

  if (action.type === "increase") {
    return {
      counter: state.counter + action.amount,
      showCounter: state.showCounter
    };
  }

  if (action.type === "decrement") {
    return {
      counter: state.counter - 1,
      showCounter: state.showCounter
    };
  }

  if (action.type === "toggle") {
    return {
      showCounter: !state.showCounter,
      counter: state.counter
    };
  }

  return state;
};

const store = createStore(counterReducer);

export default store;

調整後:

// store/index.js
import { createSlice, configureStore } from "@reduxjs/toolkit";

const initialState = { counter: 0, showCounter: true };

const counterSlice = createSlice({
  name: "counter",
  initialState,
  reducers: {
    increment(state) {
      state.counter++;
    },
    decrement(state) {
      state.counter--;
    },
    increase(state, action) {
      state.counter = state.counter + action.payload;
    },
    toggleCounter(state) {
      state.showCounter = !state.showCounter;
    }
  }
});

const store = configureStore({
  reducer: counterSlice.reducer
});

export const counterActions = counterSlice.actions;

export default store;

第二步

將 Counter 元件做調整

調整前:

// Counter.js
import { useSelector, useDispatch } from "react-redux";

const Counter = () => {
  const dispatch = useDispatch();
  const counter = useSelector((state) => state.counter);
  const show = useSelector((state) => state.showCounter);

  const incrementHandler = () => {
    dispatch({ type: "increment" });
  };

  const increaseHandler = () => {
    dispatch({ type: "increase", amount: 10 });
  };

  const decrementHandler = () => {
    dispatch({ type: "decrement" });
  };

  const toggleCounterHandler = () => {
    dispatch({ type: "toggle" });
  };

  return (
    <>
      {show && <div>{counter}</div>}
      <button onClick={incrementHandler}>Increment</button>
      <button onClick={increaseHandler}>Increase by 10</button>
      <button onClick={decrementHandler}>Decrement</button>
      <button onClick={toggleCounterHandler}>Toggle Counter</button>
    </>
  );
};

export default Counter;

調整後:

// Counter.js
import { useSelector, useDispatch } from "react-redux";

import { counterActions } from "./store/index";

const Counter = () => {
  const dispatch = useDispatch();
  const counter = useSelector((state) => state.counter);
  const show = useSelector((state) => state.showCounter);

  const incrementHandler = () => {
    dispatch(counterActions.increment());
  };

  const increaseHandler = () => {
    dispatch(counterActions.increase(10)); // { type: SOME_UNIQUE_IDENTIFIER, payload: 10 }
  };

  const decrementHandler = () => {
    dispatch(counterActions.decrement());
  };

  const toggleCounterHandler = () => {
    dispatch(counterActions.toggleCounter());
  };

  return (
    <>
      {show && <div>{counter}</div>}
      <button onClick={incrementHandler}>Increment</button>
      <button onClick={increaseHandler}>Increase by 10</button>
      <button onClick={decrementHandler}>Decrement</button>
      <button onClick={toggleCounterHandler}>Toggle Counter</button>
    </>
  );
};

export default Counter;

第三步

完成以上兩個步驟後就可以正常運作計數的程式了,當前範例如下:
範例程式

不過我們再多加一點東西進去,了解如何去管理多個 createSlice。

除了原本的檔案外,有再增加一些空白的檔案(Auth.js/auth.js/index.js),調整檔案後的結構如下:

|-- src
    |-- App.js
    |-- index.js
    |-- components
    |   |-- Counter.js
    |   |-- Auth.js
    |-- store
        |-- counter.js
        |-- auth.js
        |-- index.js

調整 App.js。

import { useSelector, useDispatch } from 'react-redux';

import Counter from './components/Counter';
import Auth from './components/Auth';
import { authActions } from './store/auth';

function App() {
  const dispatch = useDispatch();
  const isAuth = useSelector(state => state.auth.isAuthenticated);

  const logoutHandler = () => {
    dispatch(authActions.logout());
  };

  return (
    <>
      {!isAuth && <Auth />}
      {isAuth && <button onClick={logoutHandler}>Logout</button>}
      <Counter />
    </>
  );
}

export default App;

counter.js 的部分,做一些微調,註解起來的是舊的程式碼,我們要把 store 移到 store/index.js 裡去建立。

// import { createSlice, configureStore } from "@reduxjs/toolkit";
import { createSlice } from '@reduxjs/toolkit';

const initialState = { counter: 0, showCounter: true };

const counterSlice = createSlice({
  name: "counter",
  // initialState,
  initialState: initialCounterState,
  reducers: {
    increment(state) {
      state.counter++;
    },
    decrement(state) {
      state.counter--;
    },
    increase(state, action) {
      state.counter = state.counter + action.payload;
    },
    toggleCounter(state) {
      state.showCounter = !state.showCounter;
    }
  }
});

// const store = configureStore({
//   reducer: counterSlice.reducer
// });

export const counterActions = counterSlice.actions;

export default store;

接著也完成 auth.js。

import { createSlice } from '@reduxjs/toolkit';

const initialAuthState = {
  isAuthenticated: false,
};

const authSlice = createSlice({
  name: 'authentication',
  initialState: initialAuthState,
  reducers: {
    login(state) {
      state.isAuthenticated = true;
    },
    logout(state) {
      state.isAuthenticated = false;
    },
  },
});

export const authActions = authSlice.actions;

export default authSlice.reducer;

在 store/index.js 裡面建立 store,並將多個 reducer 做合併。

import { configureStore } from '@reduxjs/toolkit';

import counterReducer from './counter';
import authReducer from './auth';

const store = configureStore({
  reducer: { counter: counterReducer, auth: authReducer },
});

export default store;

Counter 元件只需修改引入 actions 的檔案。

import { useSelector, useDispatch } from "react-redux";

// import { counterActions } from "./store/index";
import { counterActions } from '../store/counter';

const Counter = () => {
  const dispatch = useDispatch();
  const counter = useSelector((state) => state.counter);
  const show = useSelector((state) => state.showCounter);

  const incrementHandler = () => {
    dispatch(counterActions.increment());
  };

  const increaseHandler = () => {
    dispatch(counterActions.increase(10)); // { type: SOME_UNIQUE_IDENTIFIER, payload: 10 }
  };

  const decrementHandler = () => {
    dispatch(counterActions.decrement());
  };

  const toggleCounterHandler = () => {
    dispatch(counterActions.toggleCounter());
  };

  return (
    <>
      {show && <div>{counter}</div>}
      <button onClick={incrementHandler}>Increment</button>
      <button onClick={increaseHandler}>Increase by 10</button>
      <button onClick={decrementHandler}>Decrement</button>
      <button onClick={toggleCounterHandler}>Toggle Counter</button>
    </>
  );
};

export default Counter;

完成 Auth 元件後,就完成這次的實作。

import { useDispatch } from "react-redux";

import { authActions } from "../store/auth";

const Auth = () => {
  const dispatch = useDispatch();

  const loginHandler = (event) => {
    event.preventDefault();

    dispatch(authActions.login());
  };

  return (
    <form onSubmit={loginHandler}>
      <div>
        <label htmlFor="email">Email</label>
        <input type="email" id="email" />
      </div>
      <div>
        <label htmlFor="password">Password</label>
        <input type="password" id="password" />
      </div>
      <button>Login</button>
    </form>
  );
};

export default Auth;

本次的實作範例在以下連結,明天將進入下一個單元:

範例程式碼


上一篇
Day16-Redux 篇-認識 Redux Toolkit
下一篇
Day18-React Router 篇-上篇
系列文
用30天更加認識 React.js 這個好朋友33
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
David
iT邦新手 5 級 ‧ 2021-10-21 11:04:59

大大您好

"在 store/index.js 裡面建立 store,並將多個 reducer 做合併"

這裡我照著做但瀏覽器回應我:index.js:1 Store does not have a valid reducer. Make sure the argument passed to combineReducers is an object whose values are reducers.

但我這樣改之後就可以了:
const store = configureStore({
reducer: {
counter: counterReducer.reducer,
auth: authReducer.reducer
}
});

沒事xD 後來我發現在 slice/counter.js 裡面 export 有加 .reducer 我沒加到,難怪會出錯~ 辛苦了。

harry xie iT邦研究生 1 級 ‧ 2021-10-21 11:49:16 檢舉

好喔~有解決就好,有問題歡迎再留言詢問

slice 會建立 action 和 reducer,slice/counter.js 內加 .reducer 就是為了匯出 reducer~

我要留言

立即登入留言